/** @file    WeatherBundle.cpp
 *  @author  Alessandro Polo
 *  @version $Id: WeatherBundle.cpp 2859 2010-08-07 00:45:36Z alex $
 *  @brief
 * File containing methods for the wosh::services::WeatherBundle class.
 * The header for this class can be found in WeatherBundle.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "WeatherBundle.h"

 #include "WeatherSourceIGoogleAPI.h"
 #include "WeatherSourceYahooAPI.h"

 #include <core/ObjectAllocator.h>
 #include <core/PersistenceManager.h>
 #include <core/MethodsCommon.h>
 #include <core/MessageProcessingCommon.h>
 #include <core/Fact.h>
 #include <core/Functional.h>
 #include <core/Utilities.h>
 #include <core/DataModels.h>
 #include <core/Table.h>
 #include <core/WoshKernel.h>


using namespace std;
namespace wosh {
 namespace services {

 WOSH_REGISTER(wosh::services::WeatherBundle, "wosh::interfaces::services::Weather", _Weather_VERSION, _static_WeatherBundle1 )
 WOSH_REGISTER(wosh::services::WeatherBundle, "wosh::BundleGeneric", _Weather_VERSION, _static_WeatherBundle2 )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

WeatherBundle::WeatherBundle()
	: BundleGeneric() {
	BundleGeneric::setName( _Weather_NAME, false );
	Properties.update( _KEY_Version, _Weather_VERSION );

Log.setLevel(LOG_DEBUG);

	this->currentWeatherSimilarityThreshold = 0.3;

	Log(LOG_DEBUG, " Configuring Weather worker.." );
	this->myWorker = new WeatherWorker(*this);
	this->myWorker->setListener(this);
	this->myWorker->addSource( new WeatherSourceIGoogleAPI() );
	this->myWorker->addSource( new WeatherSourceYahooAPI() );

	Log(LOG_DEBUG, " Setting default properties and permissions.." );
	Properties.set( _Weather_KEY_CurrentCondition, this->myWorker->getSources().count(), Permission(Permission::Read) );
	Properties.set( _Weather_KEY_CurrentWeatherTs, Variant_DT(0), Permission(Permission::Read) );
	Properties.set( _Weather_KEY_ForeCastTs, Variant_DT(0), Permission(Permission::Read) );
	Properties.set( _Weather_KEY_CurrentCondition, "", Permission(Permission::Read) );
	Properties.set( _Weather_KEY_WeatherSimilarityThreshold, this->currentWeatherSimilarityThreshold, Permission::RW_RW_R__() );

	Log(LOG_DEBUG, " Registering methods.." );
	MethodList* mmListSources = Methods.create<MethodList>( _METHOD_List, "List Weather Sources", Permission::R_XR_XR__() );
	mmListSources->setDataFieldExtractor( DataFieldExtractor<WeatherSource>::createFor< DataAdapterListT<WeatherSource> >(this->myWorker->getSources(), "ForeCasts") );

	MethodList* mmListForecast = Methods.create<MethodList>( _Weather_METHOD_get_forecast_tbl, "List Forecast Table", Permission(Permission::RX) );
	mmListForecast->setDataFieldExtractor( DataFieldExtractor<WeatherInfo>::createFor< DataAdapterMapT<int64,WeatherInfo> >(this->myWorker->getForeCast(), "ForeCasts") );

	MethodRetrieve* mmRetrieveForecast = Methods.create<MethodRetrieve>( _METHOD_Retrieve, "Retrieve Forecast(s)", Permission(Permission::RX) );
	mmRetrieveForecast->setItemExtractor( ObjectExtractor<WeatherInfo, int64>::createFor< DataAdapterMapT<int64,WeatherInfo> >(this->myWorker->getForeCast(), &WeatherInfo::getPeriodStart ) );

	MethodRetrieveObject<WeatherInfo>* mmGetWeather = Methods.create< MethodRetrieveObject<WeatherInfo> >( _Weather_METHOD_get_weather, "Get Current Weather", Permission(Permission::RX) );
	mmGetWeather->configure(&this->myWorker->getWeather(), &this->myWorker->getWeatherMux(), &this->getPermission());

	MethodRequest* mmExport = Methods.create<MethodRequest>( _Weather_METHOD_export, "export weather as XML", Permission::R_XR_XR__() );
	mmExport->setMethod( this, (MethodRequestPtr)&WeatherBundle::mmDoExport );

	setBundleState(Bundle::STATE_CREATED, false);
 }

WeatherBundle::~WeatherBundle() {
	Log(LOG_INFO, " Destroying.." );
	if ( isBundleRunning() ) {
		Log(LOG_WARNING, "~WeatherBundle() : Destroying while Bundle is running! Trying to stop.." );
		bundleStop();
	 }
	delete this->myWorker; this->myWorker = NULL;
	Log(LOG_VERBOSE, ":~WeatherBundle() : Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL

WRESULT WeatherBundle::bundleStart() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STARTING) ) return WRET_ERR_WRONG_STATE;
	WRESULT ret = BundleGeneric::start_SynchThread( this->myWorker );
	// BUNDLE-STATE (STARTED) will be updated async by WORKER, through call: thread_event()
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT WeatherBundle::bundleStop() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STOPPING) ) return WRET_ERR_WRONG_STATE;
	WRESULT ret = BundleGeneric::stop_SynchThread( this->myWorker );
	// BUNDLE-STATE (STOPPED) will be updated async by WORKER, through call: thread_event()
	return ret;
 }

////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void WeatherBundle::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;
	BundleGeneric::busMessage(message, source);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT

bool WeatherBundle::updatingProperty( bool& do_update, const Variant& value_proposed, Property& property_current, const PropertiesProvider* source )
 {
	if ( property_current.getKey() == _Weather_KEY_WeatherSimilarityThreshold ) {
		double tmpVal = value_proposed.toDouble( this->currentWeatherSimilarityThreshold );
		if ( tmpVal < 0.0 || tmpVal > 1.0 ) return false;
		this->currentWeatherSimilarityThreshold = tmpVal;
		return true;
	 }
	else // maybe is a (common) BundleGeneric property, let base implementation process it
		return BundleGeneric::updatingProperty(do_update, value_proposed, property_current, source);
 }

////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void WeatherBundle::updatedWeather( const WeatherInfo* newInfo, double similarity, WeatherWorker* ) {
	if ( newInfo == NULL ) return;

	Properties.transactionBeginWrite();
	Properties.update( _Weather_KEY_CurrentCondition, newInfo->getConditionKey() );
	Properties.update( _Weather_KEY_CurrentWeatherTs, Variant_DT(newInfo->getTimeStamp()) );
	Properties.transactionEnd();

	if ( similarity >= this->currentWeatherSimilarityThreshold ) {
		Log(LOG_DEBUG, ":updatedWeather() : Skipped Event due to similarity (%h>=%h)..", similarity, this->currentWeatherSimilarityThreshold );
		return;
	 }
	Log(LOG_VERBOSE, ":updatedWeather() : Event (similarity=%h)..", similarity );

	Message* message = new Message( new WeatherCondition_Updated( newInfo->clone(), similarity ) );
	message->setDestinationBroadcast(true);
	signMessage(message);
	BusCore.postMessage( message );
 }

void WeatherBundle::updatedForecast( const std::vector<WeatherInfo*>& , WeatherWorker* ) {

	Properties.transactionBeginWrite();
	Properties.update( _Weather_KEY_ForeCastTs, Variant_DT(Utilities::std_time()) );
	Properties.transactionEnd();

 }


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS

Response* WeatherBundle::mmDoProcessDebug( const Request* request ) {
	if ( request == NULL ) return NULL;
	Log(LOG_INFO, "mmDoProcessDebug()" );
//	this->myWorker->doasd();
	return NULL;
 }

Response* WeatherBundle::mmDoExport( const Request* request ) {
	if ( request == NULL ) return NULL;
	FilePath xmlFile;
	Variant targetFile;
	request->extractArguments(targetFile);
	if ( !targetFile.isStringNotEmpty() || !xmlFile.set(targetFile.toString()) )
		return request->replyResponse(WRET_ERR_PARAM, "Invalid argument [target filename]");
	Log(LOG_VERBOSE, ":mmDoExport() File %s ..", xmlFile.getPathLocal().c_str() );
	this->myWorker->getForeCast().transactionBeginRead();
	WRESULT ret = PersistenceManager::saveObjects( this->myWorker->getForeCast().getMap(), "XML", xmlFile.getPathLocal() );
	this->myWorker->getForeCast().transactionEnd();
	if ( WFAILED(ret) )
		return request->replyResponse(ret, "FAILED exporting data" );
	return request->replyResponse(WRET_OK, "Data Exported" );
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace services
}; // namespace wosh
